#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <debug.h>
#include <os_api.h>
#include <atserver.h>

#define TASK_STACK_SIZE   (24*1024)
static char _task_stack[TASK_STACK_SIZE];


#define APP_CMD_AT_ASYNC_RESP      1
#define APP_CMD_AT_INDICATION      2

typedef struct app_msg_s {
    uint32_t msg;
    void *data;
} app_msg_t;

typedef struct app_data_s {
    OSTaskRef task;
    OSTimerRef timer;
    OSTimerRef timer2;
    OSMsgQRef cmdq;
} app_data_t;

static app_data_t *s_ad = NULL;

static int app_send_msg(uint32_t msg_id, void *data)
{
    app_data_t *ad = s_ad;
    app_msg_t msg;

    memset(&msg, 0, sizeof(msg));
    msg.msg = msg_id;
    msg.data = data;
    if (OS_SUCCESS == OSAMsgQSend(ad->cmdq, sizeof(msg), (UINT8 *)&msg, OS_NO_SUSPEND)) {
        return 0;
    }
    return -1;
}

static void app_at_async_resp_timer_cb(UINT32 param)
{
    app_send_msg(APP_CMD_AT_ASYNC_RESP, (void *)param);
}

static void app_at_async_resp(app_data_t *ad, UINT32 atHandle)
{
    ATRESP(atHandle, ATCI_RESULT_CODE_OK, 0, NULL);
}

static void app_at_indication_timer_cb(UINT32 param)
{
    app_send_msg(APP_CMD_AT_INDICATION, (void *)IND_REQ_HANDLE);
}

static void app_at_indication_resp(app_data_t *ad, UINT32 atHandle)
{
    char buf[256];
    static unsigned count = 0;
    count++;
    if (count >= 5) {
        OSATimerStop(ad->timer2);
    }
    snprintf(buf, sizeof(buf), "+TESTIND: ABCDEFG %u", count);
    ATRESP(atHandle, 0, 0, buf);
}

static utlReturnCode_T at_testa(const utlAtParameterOp_T op, const char *command_name_p, const utlAtParameterValue_P2c parameter_values_p, const size_t num_parameters, const char *info_text_p, unsigned int *xid_p, void *arg_p)
{
    UINT32 atHandle = at_command_make_at_handle(*(TelAtParserID *)arg_p);
    int ret = utlFAILED;

    int param;
    char buf[128] = "";

    *xid_p = atHandle;

    LOG_PRINTF("%s: op: %d", __func__, op);

    switch(op) {
    case utlAT_PARAMETER_OP_GET:
        snprintf(buf, sizeof(buf), "+TESTA: This is a test");
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_OK, 0, buf);
        break;
    case utlAT_PARAMETER_OP_SET:
        if (!getExtValue(parameter_values_p, 0, &param, 0, 0x7fff, 0)) {
            ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
            break;
        }
        LOG_PRINTF("%s: param: %d", __func__, param);
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_OK, 0, NULL);
        break;
    default:
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
        break;
    }
    return ret;
}

static utlReturnCode_T at_testb(const utlAtParameterOp_T op, const char *command_name_p, const utlAtParameterValue_P2c parameter_values_p, const size_t num_parameters, const char *info_text_p, unsigned int *xid_p, void *arg_p)
{
    UINT32 atHandle = at_command_make_at_handle(*(TelAtParserID *)arg_p);
    int ret = utlFAILED;

    *xid_p = atHandle;

    LOG_PRINTF("%s: op: %d", __func__, op);

    switch(op) {
    case utlAT_PARAMETER_OP_ACTION:
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_OK, 0, NULL);
        break;
    default:
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
        break;
    }
    return ret;
}

static utlReturnCode_T at_testc(const utlAtParameterOp_T op, const char *command_name_p, const utlAtParameterValue_P2c parameter_values_p, const size_t num_parameters, const char *info_text_p, unsigned int *xid_p, void *arg_p)
{
    UINT32 atHandle = at_command_make_at_handle(*(TelAtParserID *)arg_p);
    int ret = utlFAILED;

    int param;
    unsigned param2;
    char param3[64], param4[64];
    INT16 param_len;
    char buf[128] = "";

    *xid_p = atHandle;

    LOG_PRINTF("%s: op: %d", __func__, op);

    switch(op) {
    case utlAT_PARAMETER_OP_GET:
        snprintf(buf, sizeof(buf), "+TESTC: This is a test");
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_OK, 0, buf);
        break;
    case utlAT_PARAMETER_OP_SET:
        if (!getExtValue(parameter_values_p, 0, &param, 0, 1, 0)) {
            ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
            break;
        }
        if (!getExtUValue(parameter_values_p, 1, &param2, 0, 0xffff0000, 0)) {
            ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
            break;
        }
        if (!getExtString(parameter_values_p, 2, param3, (INT16)(sizeof(param3)-1), &param_len, NULL)) {
            ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
            break;
        }
        if (!getExtString(parameter_values_p, 3, param4, (INT16)(sizeof(param4)-1), &param_len, "")) {
            ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
            break;
        }
        LOG_PRINTF("%s: param: %d", __func__, param);
        LOG_PRINTF("%s: param2: 0x%x", __func__, param2);
        LOG_PRINTF("%s: param3: %s", __func__, param3);
        LOG_PRINTF("%s: param4: %s", __func__, param4);
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_OK, 0, NULL);
        break;
    default:
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
        break;
    }
    return ret;
}

static utlReturnCode_T at_teste(const utlAtParameterOp_T op, const char *command_name_p, const utlAtParameterValue_P2c parameter_values_p, const size_t num_parameters, const char *info_text_p, unsigned int *xid_p, void *arg_p)
{
    UINT32 atHandle = at_command_make_at_handle(*(TelAtParserID *)arg_p);
    int ret = utlFAILED;

    int param;
    char buf[128] = "";

    app_data_t *ad = s_ad;

    *xid_p = atHandle;

    LOG_PRINTF("%s: op: %d", __func__, op);

    switch(op) {
    case utlAT_PARAMETER_OP_GET:
        snprintf(buf, sizeof(buf), "+TESTE: This is a test");
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_OK, 0, buf);
        break;
    case utlAT_PARAMETER_OP_SET:
        if (!getExtValue(parameter_values_p, 0, &param, 0, 0x7fff, 0)) {
            ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
            break;
        }
        LOG_PRINTF("%s: param: %d", __func__, param);
        // there is a default async AT command timeout value, current is 9 seconds. so
        // out response must shorter thatn 9 seconds.
        ret = OSATimerStart(ad->timer, SEC2TICKS(6), 0, app_at_async_resp_timer_cb, (UINT32)atHandle);
        if (ret) {
            ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
            break;
        }
        ret = 0;
        break;
    default:
        ret = ATRESP(atHandle, ATCI_RESULT_CODE_ERROR, 0, NULL);
        break;
    }
    return ret;
}

static void app_task(void *param)
{
    app_data_t *ad = (app_data_t *)param;
    int ret;
    app_msg_t msg;

    while (1) {
        memset(&msg, 0, sizeof(msg));
        ret = OSAMsgQRecv(ad->cmdq, (UINT8 *)&msg, sizeof(msg), OS_SUSPEND);
        ASSERT(ret == OS_SUCCESS);
        switch (msg.msg) {
        case APP_CMD_AT_ASYNC_RESP:
            app_at_async_resp(ad, (UINT32)msg.data);
            break;
        case APP_CMD_AT_INDICATION:
            app_at_indication_resp(ad, (UINT32)msg.data);
            break;
        default:
            break;
        }
    }
}

static utlAtParameter_T plusTestA_params[] = {
    utlDEFINE_DECIMAL_AT_PARAMETER(utlAT_PARAMETER_ACCESS_READ_WRITE, utlAT_PARAMETER_PRESENCE_REQUIRED),
};

static utlAtParameter_T plusTestC_params[] = {
    utlDEFINE_DECIMAL_AT_PARAMETER(utlAT_PARAMETER_ACCESS_READ_WRITE, utlAT_PARAMETER_PRESENCE_REQUIRED),
    utlDEFINE_HEXADECIMALL_AT_PARAMETER(utlAT_PARAMETER_ACCESS_READ_WRITE, utlAT_PARAMETER_PRESENCE_OPTIONAL),
    utlDEFINE_STRING_AT_PARAMETER(utlAT_PARAMETER_ACCESS_READ_WRITE, utlAT_PARAMETER_PRESENCE_OPTIONAL),
    utlDEFINE_QSTRING_AT_PARAMETER(utlAT_PARAMETER_ACCESS_READ_WRITE, utlAT_PARAMETER_PRESENCE_OPTIONAL),
};

static utlAtParameter_T plusTestE_params[] = {
    utlDEFINE_DECIMAL_AT_PARAMETER(utlAT_PARAMETER_ACCESS_READ_WRITE, utlAT_PARAMETER_PRESENCE_REQUIRED),
};

static utlAtCommand_T ext_at_commands[] = {
    utlDEFINE_EXTENDED_AT_COMMAND("+TESTA", plusTestA_params, "+TESTA: <a>", at_testa, at_testa),
    utlDEFINE_EXACTION_AT_COMMAND("+TESTB", NULL, "+TESTB: ", at_testb),
    utlDEFINE_EXTENDED_AT_COMMAND("+TESTC", plusTestC_params, "+TESTC: <a>[,<b>[,<c>[,<d>]]]", at_testc, at_testc),
    utlDEFINE_EXTENDED_AT_COMMAND("+TESTE", plusTestE_params, "+TESTE: <a>", at_teste, at_teste),
};

int main()
{
    app_data_t *ad;
    int ret;

    ad = malloc(sizeof(*ad));
    ASSERT(ad);
    memset(ad, 0, sizeof(*ad));

    s_ad = ad;

    ret = OSATimerCreate(&ad->timer);
    ASSERT(ret == OS_SUCCESS);

    ret = OSATimerCreate(&ad->timer2);
    ASSERT(ret == OS_SUCCESS);

    ret = OSAMsgQCreate(&ad->cmdq, "app", sizeof(app_msg_t), 10, OS_FIFO);
    ASSERT(ret == OS_SUCCESS);

    ret = OSATaskCreate(&ad->task, _task_stack, TASK_STACK_SIZE, 150, "app", app_task, ad);
    ASSERT(ret == OS_SUCCESS);

    // following command must be called here. because at this point, AT have not been initialized, we can register extend
    // AT command before AT initializatiion.
    // Note:
    //   All these AT command callback are called in AT task context, DO NOT do long time work. If you do some long time work.
    //   you may send immediate AT response in the AT command callback, do other work in your other task context, if you have AT message
    //   need to send back, you may send AT indication.
    // Note:
    //   Async AT command also will block next AT command.
    at_command_register(ext_at_commands, sizeof(ext_at_commands)/sizeof(ext_at_commands[0]));

    // we wait 20 seconds for AT task ready
    ret = OSATimerStart(ad->timer2, SEC2TICKS(20), SEC2TICKS(10), app_at_indication_timer_cb, (UINT32)ad);
    ASSERT(ret == OS_SUCCESS);

    return 0;
}
